using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using Spring.Proxy;

namespace Sedna.API.Remoting
{
    /// <summary>
    /// Builds server SAO proxy. It makes possible to inject object into proxy rather then instantiate it in proxy class.
    /// </summary>
	public class SaoProxyTypeBuilder: CompositionProxyTypeBuilder
	{
        public SaoProxyTypeBuilder() 
	    {
	    }

        /// <summary>
        /// This method has Spring1.1-like behaivour. It uses BaseType to inject constructor instead of TargetType
        /// </summary>
        /// <param name="typeBuilder"></param>
        protected override void ImplementConstructors(TypeBuilder typeBuilder)
        {
            ConstructorInfo[] constructors = BaseType.GetConstructors(
                BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

            foreach (ConstructorInfo constructor in constructors)
            {
                if (constructor.IsPublic || constructor.IsFamily)
                {
                    ConstructorBuilder cb = typeBuilder.DefineConstructor(
                        constructor.Attributes,
                        constructor.CallingConvention,
                        DefineConstructorParameters(constructor));

                    ILGenerator il = cb.GetILGenerator();
                    GenerateConstructor(cb, il, constructor);
                    il.Emit(OpCodes.Ret);
                }
            }
        }

        //todo: i'm not sure about this implementation. Perhaps, we should do the way it was in old Spring (constructor calling). But it
        //seems to work fine
        /// <summary>
        /// Constructor injection without target object creation
        /// </summary>
        /// <param name="builder"></param>
        /// <param name="il"></param>
        /// <param name="constructor"></param>
        protected override void GenerateConstructor(ConstructorBuilder builder, ILGenerator il,
                                                    ConstructorInfo constructor)
        {
            int paramCount = constructor.GetParameters().Length;
            il.Emit(OpCodes.Ldarg_0);
            for (int i = 1; i <= paramCount; i++)
            {
                il.Emit(OpCodes.Ldarg_S, i);
            }
            il.Emit(OpCodes.Call, constructor);
        }

        public static void SetTarget(object proxy, object target)
        {
            if (proxy==null)
            {
                throw new ArgumentNullException("proxy", "Proxy cannot be null");
            }
            FieldInfo targetField = proxy.GetType().GetField("__proxyTarget", BindingFlags.NonPublic | BindingFlags.Instance);
            targetField.SetValue(proxy, target);
        }
        
	}
}
